﻿// (c)2010 David Schwarz (david.schwarz@live.com)
// This file is licensed under GNU GPL v2 license, see more at http://www.gnu.org/licenses/gpl-2.0.html
namespace Z2.Core
{

	#region Using

	using System;
	using System.Timers;
	using Microsoft.Xna.Framework;
	using Microsoft.Xna.Framework.Graphics;
	using System.Collections.Generic;
	using System.Diagnostics;

	#endregion

	/// <summary>
	/// Třída, která definuje sprite
	/// </summary>
	public class Sprite : IDisposable
	{

		#region Const

		private const double DEFAULT_ANIMATION_SPEED = 50;

		#endregion

		#region Members

		private readonly object m_animationLock = new object();

		private bool m_disposed;
		private readonly ActionTimer m_timer;
		private readonly string m_alias;
		private Texture2D m_texture;
		private Vector2 m_origin;

		private Rectangle m_rect;
		private int m_frameWidth;
		private int m_frameHeight;
		private int m_frameHorizontalCount;
		private int m_frameVerticalCount;
		private SpriteAntimation m_animation;
		private SpriteEffects m_effects;

		private int m_currentFrameHorizontalIndex;
		private int m_currentFrameVerticalIndex;

		private float m_depth = Depths.Default;
		private float m_scale = 1;
		private float m_scaleDo = 0;
		private float m_scaleMax = 0;
		private float m_scaleMin = 0;
		private int m_pulsateTimes = 0;
		private bool m_isPulsating;

		private float m_angle = 0;
		private float m_rotate = 0;
		private float m_rotateTo = float.MaxValue;
		private bool m_isRotating;
		private Color m_color = Color.White;

		private List<Sprite> m_slices;
		private int m_slicesVertical;
		private int m_slicesHorizontal;

		#endregion

		#region Constructor

		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="alias">Alias spritu</param>
		/// <param name="texture">Textura</param>
		/// <param name="frameHorizontalCount">Počet snímků v ose X</param>
		/// <param name="frameVerticalCount">Počet snímků v ose Y</param>
		public Sprite(string alias, Texture2D texture, int frameHorizontalCount, int frameVerticalCount)
		{
			//this.m_animationSpeed = Sprite.DEFAULT_ANIMATION_SPEED;
			//this.m_animationTime = DateTime.Now;
			this.m_timer = new ActionTimer(Sprite.DEFAULT_ANIMATION_SPEED);
			this.m_timer.Enabled = true;

			this.m_alias = alias;

			this.m_animation = SpriteAntimation.LeftToRightOnly;

			this.m_currentFrameHorizontalIndex =
				this.m_currentFrameVerticalIndex = 0;

			this.m_frameHorizontalCount = frameHorizontalCount;
			this.m_frameVerticalCount = frameVerticalCount;

			this.m_effects = SpriteEffects.None;
			this.m_angle = 0;

			SetTexture(texture);
			

			this.OnAnimation();
			if (this.m_frameHorizontalCount > 1)
				this.StartAnimation();
		}

		#endregion

		#region Events

		public event EventHandler RotationStart;

		public event EventHandler RotationStop;

		public event EventHandler PulsateStart;

		public event EventHandler PulsateStop;

		#endregion

		#region Methods

		public void CreateSlices(int vertical, int horizontal)
		{
			if(this.m_slices != null)
			{
#if DEBUG
				//Debug.WriteLine(string.Format("Slices already created for {0}.", Alias));
#endif
				return;
			}

			this.m_slices = new List<Sprite>();

			this.m_slicesVertical = vertical;
			this.m_slicesHorizontal = horizontal;

			int sliceW = this.Texture.Width / horizontal;
			int sliceH = this.Texture.Height / vertical;

			for (int i = 0; i < horizontal; ++i)
			for (int j = 0; j < vertical; ++j)
			{
				Rectangle source = new Rectangle(i * sliceW, j * sliceH, sliceW, sliceH);
				Texture2D slicedTexture = null;

				// Look in the slice cache first
				if (SpriteManager.Instance.ContainsSliceCahce(this.Alias, source))
				{
					// Get cached texture
					slicedTexture = SpriteManager.Instance.GetSliceCache(this.Alias, source);
					//Debug.WriteLine(string.Format("Geting sliced texture from cache, {0}, {1}.", this.Alias, source));
				}
				else
				{
					// Create sliced texture.
					// This process is kinda slow, so we must add the texture to the cache
					RenderTarget2D target = SpriteManager.CopyTexture2D(this.Texture.GraphicsDevice, this.Texture, source);
					if (target == null)
					{
						// Throw exception? Anyway, this shouldn't happen.
						return;
					}
					// Get texture from render target
					slicedTexture = target.GetTexture();
					SpriteManager.Instance.AddSliceCache(slicedTexture, source, this.Alias); // Add it to cache
				}

				Sprite slicedSprite = new Sprite(Guid.NewGuid().ToString(), slicedTexture, 1, 1);
				slicedSprite.Depth = this.Depth;
				this.m_slices.Add(slicedSprite);

				SpriteManager.Instance.Register(slicedSprite, "slices");
			}
		}

		public void Update()
		{
			if (!this.m_timer.Now()) return;

			switch (this.m_animation)
			{
				case SpriteAntimation.LeftToRightOnly:
					++this.m_currentFrameHorizontalIndex;
					if (this.m_currentFrameHorizontalIndex >= this.m_frameHorizontalCount)
					{
						this.m_currentFrameHorizontalIndex = 0;
					}
					break;
			}

			this.OnAnimation();
		}

		protected void OnPulsateStop()
		{
			if (this.PulsateStop == null)
			{
				return;
			}
			this.PulsateStop(this, EventArgs.Empty);
		}

		protected void OnPulsateStart()
		{
			if (this.PulsateStart == null)
			{
				return;
			}
			this.PulsateStart(this, EventArgs.Empty);
		}

		protected void OnRotationStart()
		{
			if (this.RotationStart == null)
			{
				return;
			}
			this.RotationStart(this, EventArgs.Empty);
		}

		protected void OnRotationStop()
		{
			if (this.RotationStop == null)
			{
				return;
			}
			this.RotationStop(this, EventArgs.Empty);
		}


		public void Pulsate(float pulsateDiff, float max, float min, int times)
		{
			this.m_scaleDo = pulsateDiff;
			this.m_scaleMax = max;
			this.m_scaleMin = min;
			this.m_pulsateTimes = times;
			this.m_isPulsating = true;
			this.OnPulsateStart();
		}

		public void RotateStop()
		{
			this.m_isRotating = false;
			this.Rotate(0);
			this.OnRotationStop();
		}

		public void Rotate(float rotate)
		{
			this.m_rotate = rotate;
			this.m_rotateTo = float.MaxValue;
			this.m_isRotating = true;
			this.OnRotationStart();
		}

		public void RotateTo(float rotate, float to)
		{
			this.m_rotate = rotate;
			this.m_rotateTo = to;
			this.m_isRotating = true;
			this.OnRotationStart();
		}

		/// <summary>
		/// Metoda upraví přenosový RECT podle aktuálních hodnot
		/// </summary>
		private void OnAnimation()
		{
			if (this.m_rotate != 0)
			{
				this.m_angle += this.m_rotate;
				if (this.m_angle >= this.m_rotateTo)
				{
					this.m_isRotating = false;
					this.m_rotate = 0;
					this.m_angle = this.m_rotateTo;
					this.OnRotationStop();
				}
			}

			if (this.m_scaleDo != 0)
			{
				if (this.m_pulsateTimes <= 0)
				{
					this.m_scaleMax =
						this.m_scaleMin = 0;
					this.m_scale += (this.m_scale + this.m_scaleDo) > 1 ? -this.m_scaleDo : this.m_scaleDo;
					if (Math.Round(this.m_scale, 1, MidpointRounding.ToEven) == 1)
					{
						this.m_scaleDo = 0;
						this.m_isPulsating = false;
						this.OnPulsateStop();
					}
				}
				else
				{
					bool scalingUp = this.m_pulsateTimes % 2 == 0;
					if (scalingUp)
					{
						this.m_scale += this.m_scaleDo;
						if (this.m_scale >= this.m_scaleMax)
						{
							--this.m_pulsateTimes;
						}
					}
					else
					{
						this.m_scale -= this.m_scaleDo;
						if (this.m_scale <= this.m_scaleMin)
						{
							--this.m_pulsateTimes;
						}
					}
				}
			}

			lock (this.m_animationLock)
			{
				this.m_rect = new Rectangle(
					this.m_currentFrameHorizontalIndex * this.m_frameWidth,
					this.m_currentFrameVerticalIndex * this.m_frameHeight,
					this.m_frameWidth, this.m_frameHeight);
			}
		}

		public void StopAnimation()
		{
			this.m_timer.Enabled = false;
		}

		/// <summary>
		/// Metoda spustí animaci
		/// </summary>
		public void StartAnimation()
		{
			this.m_timer.Enabled = true;
		}

		/// <summary>
		/// Metoda pozastaví animaci
		/// </summary>
		public void PauseAnimation()
		{
			this.m_timer.Enabled = false;
		}

		/// <summary>
		/// Metoda pozastaví animaci a nastaví konkrétní snímek
		/// </summary>
		/// <param name="horizontalIndex">Snímek v ose X</param>
		/// <param name="verticalIndex">Snímek v ose Y</param>
		public void PauseAnimation(int horizontalIndex, int verticalIndex)
		{
			this.m_timer.Enabled = false;
			this.FrameHorizontalIndex = horizontalIndex;
			this.FrameVerticalIndex = verticalIndex;
		}

		/// <summary>
		/// Metoda vykreslí sprite
		/// </summary>
		/// <param name="batch"><see cref="SpriteBatch"/></param>
		/// <param name="position">Pozice spritu na obrazovce</param>
		public void Draw(SpriteBatch batch, Vector2 position)
		{
			batch.Draw(this.m_texture, Vector2.Add(position, this.m_origin), this.m_rect, m_color,
				this.m_angle, this.m_origin, this.m_scale, this.m_effects, this.m_depth);
		}

		public void Draw(SpriteBatch batch, Vector2 position, Rectangle rect, Color color)
		{
			batch.Draw(this.m_texture, Vector2.Add(position, this.m_origin), rect, color,
				this.m_angle, this.m_origin, this.m_scale, this.m_effects, this.m_depth);
		}

		#endregion

		#region Properties

	    public int SlicesVertical
	    {
            get { return this.m_slicesVertical; }
	    }

	    public int SlicesHorizontal
	    {
            get { return this.m_slicesHorizontal; }
	    }

	    public List<Sprite> Slices
	    {
            get { return this.m_slices; }
	    }

	    public float Depth
	    {
	        get { return m_depth; }
            set { m_depth = value; }
	    }

		public float Visibility
		{
			get
			{
				return ((float)m_color.A) * (1.0f / 255.0f);
			}
			set
			{
				m_color = new Color(1.0f, 1.0f, 1.0f, value);
			}
		}

		public bool IsPulsating
		{
			get
			{
				return this.m_isPulsating;
			}
		}

		public bool IsRotating
		{
			get
			{
				return this.m_isRotating;
			}
		}

		/// <summary>
		/// Vrací šířku snímku
		/// </summary>
		public int FrameWidth
		{
			get
			{
				return this.m_frameWidth;
			}
		}

		/// <summary>
		/// Vrací výšku snímku
		/// </summary>
		public int FrameHeight
		{
			get
			{
				return this.m_frameHeight;
			}
		}

		/// <summary>
		/// Vrací celk. šířku
		/// </summary>
		public int Width
		{
			get
			{
				return this.m_texture.Width;
			}
		}

		/// <summary>
		/// Vrací celk. výšku
		/// </summary>
		public int Height
		{
			get
			{
				return this.m_texture.Height;
			}
		}

		/// <summary>
		/// Vrací alias spritu
		/// </summary>
		public string Alias
		{
			get
			{
				return this.m_alias;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje efekt
		/// </summary>
		public SpriteEffects Effects
		{
			get
			{
				return this.m_effects;
			}
			set
			{
				this.m_effects = value;
				this.OnAnimation();
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje úhel rotace
		/// </summary>
		public float Angle
		{
			get
			{
				return this.m_angle;
			}
			set
			{
				this.m_angle = value;
				this.OnAnimation();
			}
		}

		/// <summary>
		/// Nastaví nebo vrátí rychlost animace
		/// </summary>
		public double AnimationSpeed
		{
			get
			{
				return this.m_timer.Interval;
			}
			set
			{
				this.m_timer.Interval = value;
			}
		}

		/// <summary>
		/// Vrací přenosový rect
		/// </summary>
		public Rectangle Rectangle
		{
			get
			{
				lock (this.m_animationLock)
				{
					return this.m_rect;
				}
			}
		}
		public float Scale
		{
			get
			{
				return m_scale;
			}
			set
			{
				m_scale = value;
			}
		}

		/// <summary>
		/// Vrací texturu
		/// </summary>
		public Texture2D Texture
		{
			get
			{
				return this.m_texture;
			}
		}

		/// <summary>
		/// Vrací aktuální snímek v ose X
		/// </summary>
		public int FrameHorizontalIndex
		{
			get
			{
				return this.m_currentFrameHorizontalIndex;
			}
			set
			{
				this.m_currentFrameHorizontalIndex = value;
				this.OnAnimation();
			}
		}

		/// <summary>
		/// Vrací aktuální snímek v ose Y
		/// </summary>
		public int FrameVerticalIndex
		{
			get
			{
				return this.m_currentFrameVerticalIndex;
			}
			set
			{
				this.m_currentFrameVerticalIndex = value;
				this.OnAnimation();
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje typ animace
		/// </summary>
		public SpriteAntimation Animation
		{
			get
			{
				return this.m_animation;
			}
			set
			{
				this.m_animation = value;
				this.OnAnimation();
			}
		}

		#endregion

		#region Event Handling

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if (this.m_disposed)
			{
				return;
			}

			this.m_timer.Enabled = false;

			this.m_disposed = true;
			GC.SuppressFinalize(this);
			GC.WaitForPendingFinalizers();
		}

		#endregion


		internal void SetTexture(Texture2D newTexture)
		{
			m_texture = newTexture;
			this.m_frameWidth = this.m_texture.Width / this.m_frameHorizontalCount;
			this.m_frameHeight = this.m_texture.Height / this.m_frameVerticalCount;
			this.m_origin = new Vector2(this.m_frameWidth / 2.0f, this.m_frameHeight / 2.0f);
		}
	}
}
